/*
 * SimplePoolExecutor.java
 *
 * Created on April 7, 2005, 10:05 PM
 */
package util;


import driver.AppLog;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import web.WebQueryInstance;


/**
 *
 * @author Keith
 */
public class SimplePoolExecutor extends ThreadPoolExecutor {

  private final Logger myLogger = AppLog.appLog;

  private static class ExpirationTimer {

    private final long expirationDuration;
    private long lastReading;
    private long expirationTime;


    public ExpirationTimer( long expirationDuration ) {
      this.expirationDuration = expirationDuration;

      this.lastReading = 0;

      this.expirationTime = System.currentTimeMillis() + expirationDuration;
    }


    public boolean isExpired( long currentReading ) {
      boolean timerExpired = false;

      if ( currentReading == lastReading ) {
        // if the currentReading is the same as the lastReading, has the time expired?
        timerExpired = System.currentTimeMillis() > expirationTime;
      } else {
        // if the current reading is different, reset values
        lastReading = currentReading;
        expirationTime = System.currentTimeMillis() + expirationDuration;
      }

      return timerExpired;
    }
  };
  private Set<Runnable> runSet = Collections.synchronizedSet( 
    new HashSet<Runnable>() );


  /** Creates a new instance of SimplePoolExecutor */
  public SimplePoolExecutor() {
    super( 9, 15, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
  }


  public boolean awaitExecution() {

    final long maxWaitDuration = 1 * 60 * 1000L;

    boolean done;
    ExpirationTimer timer = new ExpirationTimer( maxWaitDuration );
    int activeTaskCnt;

    do {
      long completedTaskCnt = getCompletedTaskCount();
      long totalTaskCnt = getTaskCount();

      activeTaskCnt = getActiveCount();
      myLogger.log( Level.FINE,
                    activeTaskCnt + " active (" + runSet.size() + "), " +
                    completedTaskCnt + " completed, " + totalTaskCnt + " total." );

      try {
        awaitTermination( 2L, TimeUnit.SECONDS );
      } catch ( InterruptedException e ) {
      }

      done =
        ( ( completedTaskCnt != 0 && activeTaskCnt == 0 ) || timer.isExpired( 
        completedTaskCnt ) );

    } while ( !done );

    if ( activeTaskCnt > 0 ) {
      for ( Runnable r : runSet ) {
        WebQueryInstance wqi = (WebQueryInstance) r;

        try {
          myLogger.log( Level.SEVERE,
                        "Incomplete Task!: wqi: params: " + wqi.getParams() + ", url: " + wqi.getUrl() );
        } catch ( Exception e ) {
          e.printStackTrace();
        }
        wqi.interrupt();
      }
    }

    return activeTaskCnt > 0;
  }


  @Override
  public void execute( Runnable command ) {
//        System.out.println( "command: " + command.toString() );
    super.execute( command );
  }


  /**
   * Method invoked upon completion of execution of the given
   * Runnable.  This method is invoked by the thread that executed
   * the task. If non-null, the Throwable is the uncaught exception
   * that caused execution to terminate abruptly. Note: To properly
   * nest multiple overridings, subclasses should generally invoke
   * <tt>super.afterExecute</tt> at the beginning of this method.
   * 
   * @param r the runnable that has completed.
   * @param t the exception that caused termination, or null if
   * execution completed normally.
   */
  @Override
  protected void afterExecute( Runnable r, Throwable t ) {
    super.afterExecute( r, t );

    runSet.remove( r );
  }


  /**
   * Method invoked prior to executing the given Runnable in the
   * given thread.  This method is invoked by thread <tt>t</tt> that
   * will execute task <tt>r</tt>, and may be used to re-initialize
   * ThreadLocals, or to perform logging. Note: To properly nest
   * multiple overridings, subclasses should generally invoke
   * <tt>super.beforeExecute</tt> at the end of this method.
   * 
   * @param t the thread that will run task r.
   * @param r the task that will be executed.
   */
  @Override
  protected void beforeExecute( Thread t, Runnable r ) {
    runSet.add( r );

    super.beforeExecute( t, r );
  }
}
